﻿using System;
using System.Configuration;
using System.IO;
using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.LinearAlgebra.Generic;
using innovations.ml.core.models;
namespace innovations.ml.data
{
    /// <summary>
    /// Loads handwritten digit data from http://yann.lecun.com/exdb/mnist/
    /// </summary>
    public class MNISTLoader
    {
        public void Load(int numberOfRecords = -1)
        {
            this.X = LoadX(numberOfRecords);
            this.Y = LoadY(numberOfRecords);
        }

        private Matrix<double> LoadX(int numberOfRecords = -1)
        {
            string imageFile = ConfigurationManager.AppSettings["MNISTImagesFile"].ToString();
            byte[] images = File.ReadAllBytes(imageFile);
            int magicNumber;
            int imageCount;
            int rowCount;
            int columnCount;
            double[,] imageDoubleArray = null;

            using (MemoryStream memory = new MemoryStream(images))
            {
                using (BinaryReader reader = new BinaryReader(memory))
                {
                    byte[] magicNumberArray = reader.ReadBytes(OFFSET_SIZE);
                    byte[] imageNumberArray = reader.ReadBytes(ITEMS_SIZE);
                    byte[] rowCountArray = reader.ReadBytes(ROWS_SIZE);
                    byte[] columnCountArray = reader.ReadBytes(COLUMNS_SIZE);

                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(magicNumberArray);
                        Array.Reverse(imageNumberArray);
                        Array.Reverse(rowCountArray);
                        Array.Reverse(columnCountArray);
                    }

                    magicNumber = BitConverter.ToInt32(magicNumberArray, 0);
                    imageCount = BitConverter.ToInt32(imageNumberArray, 0);
                    rowCount = BitConverter.ToInt32(rowCountArray, 0);
                    columnCount = BitConverter.ToInt32(columnCountArray, 0);
                    if (numberOfRecords == -1)
                        numberOfRecords = imageCount;
                    imageDoubleArray = new double[numberOfRecords, IMAGE_SIZE];
                    for (int i = 0; i < numberOfRecords; i++)
                    {
                        byte[] imageArray = reader.ReadBytes(IMAGE_SIZE);
                        for (int j = 0; j < IMAGE_SIZE; j++)
                            imageDoubleArray[i, j] = Convert.ToDouble(imageArray[j].ToString());
                    }
                    return new DenseMatrix(imageDoubleArray);
                }
            }
        }

        private Vector<double> LoadY(int numberOfRecords = -1)
        {
            int magicNumber;
            int labelCount;

            string labelsFile = ConfigurationManager.AppSettings["MNISTLabelsFile"].ToString();
            byte[] labels = File.ReadAllBytes(labelsFile);
            double[] labelsDouble = null;
            using (MemoryStream memory = new MemoryStream(labels))
            {
                using (BinaryReader reader = new BinaryReader(memory))
                {
                    byte[] magicNumberArray = reader.ReadBytes(OFFSET_SIZE);
                    byte[] labelNumberArray = reader.ReadBytes(ITEMS_SIZE);

                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(magicNumberArray);
                        Array.Reverse(labelNumberArray);
                    }

                    magicNumber = BitConverter.ToInt32(magicNumberArray, 0);
                    labelCount = BitConverter.ToInt32(labelNumberArray, 0);
                    if (numberOfRecords == -1)
                        numberOfRecords = labelCount;
                    labelsDouble = new double[numberOfRecords];

                    for (int i = 0; i < numberOfRecords; i++)
                        labelsDouble[i] = reader.ReadByte();
                }
            }
            return new DenseVector(labelsDouble);
        }

        private static int OFFSET_SIZE = 4; //in bytes
        private static int LABEL_MAGIC = 2049;
        private static int IMAGE_MAGIC = 2051;
        private static int ITEMS_SIZE = 4;
        private static int ROWS_SIZE = 4;
        private static int ROWS = 28;
        private static int COLUMNS_SIZE = 4;
        private static int COLUMNS = 28;
        private static int IMAGE_SIZE = ROWS * COLUMNS;
        public Matrix<double> X { get; set; }
        public Vector<double> Y { get; set; }
    }
}
